<?php

/**
 * @filename Workflow.class.php 
 * @encoding UTF-8 
 * @author nemo.xiaolan <a href="mailto:335454250@qq.com">335454250@qq.com</a>
 * @link <a href="http://www.sep-v.com">http://www.sep-v.com</a>
 * @license http://www.sep-v.com/code-license
 * @datetime 2013-11-15  9:23:03
 * @Description
 *   
 * 工作流程序处理基类
 */
class Workflow {
    
    protected $currentWorkflow;
    
    protected $nodes;
    
    protected $nodeModel;
    
    protected $processModel;
    
    protected $context;
    
    public function __construct($workflowAlias, $context=array()) {
        $this->workflowAlias = $workflowAlias;
        $this->context = $context;
        $this->currentWorkflow = D("Workflow")->getByAlias($workflowAlias);//("alias='".$workflowAlias."'")->find();
        if(!$this->currentWorkflow) {
            //@todo
        }
        $this->nodeModel = D("WorkflowNode");
        $this->processModel = D("WorkflowProcess");
    }
    
    /**
     * 根据流程节点获取所有处于当前节点的process
     */
    public function getRowIdsByNode($id_or_alias, $field="alias") {
        $field = $field == "alias" ? "execute_file" : "id";
        if(is_array($id_or_alias)) {
            $id_or_alias = implode(",", $id_or_alias);
        }
        if("id" !== $field) {
            $map = array(
                $field => array("IN", $id_or_alias),
                "workflow_id" => $this->currentWorkflow["id"]
            );
            $theNodes = $this->nodeModel->where($map)->select();
            foreach($theNodes as $tn) {
                $tmp[] = $tn["id"];
            }
            $id_or_alias = implode(",", $tmp);
        }
        
        
        $map = array(
            "node_id" => array("IN", $id_or_alias),
            "status" => 0
        );
        
        $data = $this->processModel->where($map)->select();
        foreach($data as $v) {
            $re[] = $v["mainrow_id"];
        }
        
        return $re;
    }
    
    /**
     * 获取当前流程所处进程
     */
    public function getCurrentProcess($mainRowid) {
        $map = array(
            "mainrow_id" => $mainRowid,
            "workflow_id"=> $this->currentWorkflow["id"]
        );
        $process = $this->processModel->where($map)->order("id DESC")->find();
        if(!$process) {
            return false;
        }
        $currentNode = $this->nodeModel->find($process["node_id"]);
        if(!$currentNode) {
            return false;
        }
        
        $map = array(
            "workflow_id" => $this->currentWorkflow["id"],
            "id" => array("IN", implode(",",array($currentNode["prev_node_id"],$currentNode["next_node_id"]))),  //prev_id,next_id
//            "type" => array("NEQ", 3) //排除等待外部响应的节点
        );
        $tmp = $this->nodeModel->where($map)->select();
        foreach($tmp as $k=> $an) {
            $allNodes[$an["id"]] = $an;
        }
        
        $prevNode = explode(",", $currentNode["prev_node_id"]);
        foreach($prevNode as $pn) {
            if($pn and $this->checkExecutorPermission($allNodes[$pn]["executor"])) {
                $process["prevNode"][$pn] = $allNodes[$pn];
            }
        }
        $nextNode = explode(",", $currentNode["next_node_id"]);
        
        foreach($nextNode as $pn) {
            if($pn and $this->checkExecutorPermission($allNodes[$pn]["executor"]) and $this->checkCondition($mainrowId, $allNodes[$pn])) {
                $process["nextNode"][$pn] = $allNodes[$pn];
                if($allNodes[$pn]["type"] != 3){ //外部响应节点
                    $process["nextActions"][$pn] = $allNodes[$pn];
                }
            }
        }
        
        $map = array(
            "status" => 1,
            "mainrow_id" => $mainRowid,
            "workflow_id" => $this->currentWorkflow["id"],
        );
        $prevProcess = $this->processModel->where($map)->order("id DESC")->find();
//        echo $this->processModel->getLastSql();exit;
        
        $process["currentNode"] = $currentNode;
        $process["prevProcess"] = $prevProcess;
        
        return $process;
    }
    
    /**
     * 获取列表中所有信息的所处流程，包括流程上下文
     * 1、获取当前已进行到的流程
     * 2、根据当前流程next_node_id获取可以执行的下一流程 2,3
     * 3、@todo 判断当前用户是否有权限进行下一操作
     */
    public function getListProcess($mainRowids) {
        //所有已进行流程 ?当前进行中流程
        $processViewModel = D("WorkflowProcessView");
        $tmp = $processViewModel->where(array(
            "mainrow_id" => array("IN", implode(",", $mainRowids)),
            "workflow_id" => $this->currentWorkflow["id"],
            "status" => 0
        ))->order("start_time ASC")->select();
//        print_r($tmp);exit;
        //process 无记录，则寻找第一个node
        if(!$tmp) {
            return array();
        }
        //所有需用到的node
        foreach($tmp as $k=>$v) {
            $processes[$v["mainrow_id"]] = $v;
            $processes[$v["mainrow_id"]]["nextNodes"] = explode(",", $v["next_node_id"]);
            $processes[$v["mainrow_id"]]["prevNodes"] = explode(",", $v["prev_node_id"]);
            if($v["node_id"]) {
                $ids[$v["node_id"]] = $v["node_id"];
            }
            if($v["prev_node_id"]) {
                $ids[$v["prev_node_id"]] = $v["prev_node_id"];
            }
            if($v["next_node_id"]) {
                $ids[$v["next_node_id"]] = $v["next_node_id"];
            }
        }
        $tmp = $this->nodeModel->where(array(
            "workflow_id" => $this->currentWorkflow["id"],
            "id" => array("IN", implode(",", $ids)),
//            "type" => array("NEQ", 3) //排除等待外部响应的节点
        ))->select();
//        print_r($ids);exit;
        foreach($tmp as $k=>$v) {
            $theNodes[$v["id"]] = $v;
        }
        
        //获取所有process的上一process
        $map = array(
            "workflow_id" => $this->currentWorkflow["id"],
            "mainrow_id" => array("IN", implode(",", $mainRowids)),
            "status" => 1,
        );
        
        $sql = "SELECT a.* FROM (SELECT b.* FROM __TABLE__ b ORDER BY id DESC) a
                WHERE a.mainrow_id IN (%s) AND a.status=1 GROUP BY a.mainrow_id";
        $sql = sprintf($sql,implode(",", $mainRowids));
        $tmp = $this->processModel->query($sql);
        foreach($tmp as $p) {
            $prevProcess[$p["mainrow_id"]] = $p;
        }
        
//        print_r($theNodes);exit;
//        print_r($processes);
        // 给相应流程node加入执行上下文
        foreach($processes as $k=>$v) {
            $processes[$k]["currentNode"] = $theNodes[$v["node_id"]];
            foreach($v["prevNodes"] as $i=>$pn) {
                if(!$pn or !$this->checkExecutorPermission($theNodes[$pn]["executor"])) {
                    unset($processes[$k]["nextNodes"][$i]);
                    continue;
                }
                
                $processes[$k]["prevNodes"][$i] = $theNodes[$pn];
            }
            foreach($v["nextNodes"] as $i=>$pn) {
                if(!$pn or !$this->checkExecutorPermission($theNodes[$pn]["executor"]) // 检查工作流执行权限
                        or !$this->checkCondition($k, $theNodes[$pn])) { //检查节点条件
                    unset($processes[$k]["nextNodes"][$i]);
                    continue;
                }
                $processes[$k]["nextNodes"][$i] = $theNodes[$pn];
                if($theNodes[$pn]["type"] != 3){ //外部响应节点
                    $processes[$k]["nextActions"][$i] = $theNodes[$pn];
                }
            }
            if(array_key_exists($v["mainrow_id"], $prevProcess)) {
                $processes[$k]["prevProcess"] = $prevProcess[$v["mainrow_id"]];
            }
        }
        
        return $processes;
        
    }
    
    /**
     * 获取当前需要执行的动作
     * @todo 判断当前流程状态是否可以执行node_id动作
     */
    public function getCurrentNode ($mainrow_id,$node_id="",$ignoreCheck=false) {
        if($node_id) {
            $node = $this->nodeModel->find($node_id);
            //当前节点上文
            $map = array(
                "workflow_id" => $this->currentWorkflow["id"],
                "mainrow_id"  => $mainrow_id
            );
            $currentProcess = $this->processModel->where($map)->order("start_time DESC")->find();
        } else {
            $map = array(
                "workflow_id" => $this->currentWorkflow["id"],
                "mainrow_id"  => $mainrow_id
            );
            //判断proccess表中是否有数据
            $currentProcess = $this->processModel->where($map)->order("start_time DESC")->find();
            unset($map["mainrow_id"]);
            if(!$currentProcess) {
                $node = $this->nodeModel->where($map)->order("listorder ASC")->limit(1)->find();
            } else {
                $prevNode = $this->nodeModel->find($currentProcess["node_id"]);
                $map["listorder"] = array(
                    "GT", $prevNode["listorder"]
                );
                $node = $this->nodeModel->where($map)->order("listorder ASC")->find();
            }
        }
//        echo $this->nodeModel->getLastSql();exit;
//        var_dump($node);exit;
        if(!$node and !$ignoreCheck) {
            throw_exception(L("workflow_not_found"));
            return false;
        }
        
        if(false === $ignoreCheck) {
            if(!$this->checkExecutorPermission($node["executor"])) {
                throw_exception(L("workflow_permission_denied"));
                return false;
            }
        }
        
        $currentProcess["context"] = unserialize($currentProcess["context"]);
        
        $file = sprintf("@.Workflow.%s.%s", $this->currentWorkflow["workflow_file"], $node["execute_file"]);
        import("@.Workflow.WorkflowInterface");
        import("@.Workflow.WorkflowAbstract");
        $rs = import($file);
        $className = $this->currentWorkflow["workflow_file"].$node["execute_file"];
        if(!$className or !class_exists($className)) {
            throw_exception(sprintf(L("class_not_found")." %s", $className));
            return false;
        }
        $node["context"] = unserialize($node["context"]);
        $obj = new $className($mainrow_id, $currentProcess["context"]);
        
//        var_dump($obj);exit;
        $obj->currentNode = $node;
        $obj->context = $currentProcess["context"];
        $obj->currentWorkflow = $this->currentWorkflow;
        $obj->init($mainrow_id);
//        var_dump($obj);exit;
        return $obj;
    }
    
    /**
     * 执行下一流程
     * @todo 判断当前流程是否已执行，防止出现多人多次执行 【驳回怎么办？ context】 
     * 判断依据：1、next->current.listorder==0，说明为新建 存在process即返回
     * 2、 最新进程listorder >= 需执行进程listorder 初步认定为已执行过，判断上一条listorder
     *    如果上一条listorder > 当前listorder 认定为驳回 重新处理，否则为已处理过
     * 3、 如果是自动执行，则不检查 $auto 参数
     *        
     */
    public function doNext($mainRowid, $nodeId="", $ignoreCheck=false, $auto=true) {
        $next = $this->getCurrentNode($mainRowid, $nodeId, $ignoreCheck);
        if(!$next) {
            return false; //@todo
        }
        
        $map = array(
            "workflow_id" => $this->currentWorkflow["id"],
            "mainrow_id" => $mainRowid
        );
        $hasProcessed = false;
        if(false === $auto) {
            if(0 == $next->currentNode["listorder"]) {
                $hasProcessed = $this->processModel->where($map)->find();
            } else {
                $tmp = $this->processModel->where($map)->order("id DESC")->find();
                $node = $this->nodeModel->find($tmp["node_id"]);
                if($node["listorder"] >= $next->currentNode["listorder"]) {
                    $map["id"] = array("LT", $tmp["id"]);
                    $prevProcess = $this->processModel->where($aMap)->order("id DESC")->find();
                    $prevNode = $this->nodeModel->find($prevProcess["node_id"]);
                    if($prevNode and $prevNode["listorder"] > $next->currentNode["listorder"]) {
                        $hastProcessed = false;
                    } else {
                        $hasProcessed = true;
                    }
                }
            }
        }
        
        if($hasProcessed) {
            return true;
        }
        
        if(!$this->checkCondition($mainRowid, $next->currentNode)) {
            return false;
        }
        
//        echo "not processed";
//        exit;
        
//        echo $nodeId;
        $rs = $next->run();
        
//        var_dump($next);
        $context = $next->context ? $next->context : $this->context;
        if(true === $rs or !$rs) {
            $this->afterRun($mainRowid, $next->currentNode["id"], $context, $next);
        }
        return $rs;
    }
    
    /**
     * 当前工作流程逻辑执行结束后，保存当前状态进process表
     * 1、更新上一条的end_time 表明上一动作结束时间,更新上一条status为1 标示为已完成
     * 2、插入新数据，end_time为空
     * 3、判断下一节点是否为自动执行
     */
    public function afterRun($mainrowId, $nodeId, $context="", $next="") {
        
        $context ? $context : $this->context;
//        echo 2;
//        echo serialize($context);
//        print_r($context);exit;
        
        $map = array(
            "mainrow_id" => $mainrowId,
            "workflow_id"=> $this->currentWorkflow["id"]
        );
        $prevProcess = $this->processModel->where($map)->order("id DESC")->limit(1)->find();
//        echo $this->processModel->getLastSql();exit;
        if($prevProcess) {
            $data = array(
                "id" => $prevProcess["id"],
                "status" => 1,
                "end_time" => CTS
            );
            $this->processModel->save($data);
//            echo $this->processModel->getLastSql();exit;
        }
        $memo = $context["memo"] ? $context["memo"] : ($next->memo ? $next->memo : "");
        unset($context["memo"]);
        $data = array(
            "workflow_id" => $this->currentWorkflow["id"],
            "node_id" => $nodeId,
            "mainrow_id" => $mainrowId,
//            "context" => "", //@todo 流程上下文
            "context" => serialize($context),
            "start_time" => CTS,
            "end_time" => "",
            "status"  => "0",
            "user_id" => $_SESSION["user"]["id"],
            "memo" => $memo
        );
        
        $this->processModel->add($data);
        
        /**
         * 下一节点
         */
        $curNode = $this->nodeModel->find($nodeId);
        //如果有多个动作，默认执行第一个动作（分支，条件判断等）
        //此处应为递归操作
        
        if($curNode["next_node_id"]) {
            if(false === strpos($curNode["next_node_id"], ",")) {
                $map = array(
                    "id" => $curNode["next_node_id"],
                );
//                print_r($curNode);
            } else {
                $map = array(
                    "id" => array("IN", $curNode["next_node_id"]),
                    "default" => 1
                );
            }
            $nextNode = $this->nodeModel->where($map)->find();
//            print_r($curNode);
//            echo $this->nodeModel->getLastSql();
//            print_r($nextNode);exit;
            if($nextNode) {
                switch($nextNode["type"]) {
                    case "2":
                        $this->doNext($mainrowId, $nextNode["id"], true);
                        //判断条件：如果是之后流程驳回，则不自动进行
                        //1、存在排序>当前节点的了流程 2、有memo？
//                        $map = array(
//                            "workflow_id" => $this->currentWorkflow["id"],
//                            "mainrow_id" => $mainrowId,
//                            "listorder" => array("GT", $nextNode["listorder"]),
//                        );
//                        $processViewModel = D("WorkflowProcessView");
//                        $rs = $processViewModel->where($map)->select();
//                        print_r($nextNode);exit;
//                        echo $processViewModel->getLastSql();
//                        var_dump($rs);exit;
//                        if(!$rs) {
//                            echo $mainrowId;exit;
//                            echo $nextNode["id"];
//                            $next = $this->getCurrentNode($mainrowId, $nextNode["id"], true);
////                            var_dump($next);exit;
//                            $next->run();
//                            $this->afterRun($mainrowId, $nextNode["id"]);
//                        }
                        break;
                    default:
                        return;
                }
            }
        }
        
    }
    
    /**
     * 获取某条数据所经过的所有工作流程
     * @param $data = array(
     *  "workflowAlias" => "mainrow_id"
     * );
     */
    public function getItemProcesses($model, $id, $relationModels=array(), $relationMainrowField="source_id") {
        
        $workflowModel = D("Workflow");
        $models = array_merge((array)$relationModels, array($model));
        $workflows = $workflowModel->where(array(
            "workflow_file" => array("IN", implode(",", $models))
        ))->select();
        
        foreach($workflows as $v) {
            $theWorkflows[$v["workflow_file"]] = $v["id"];
        }
        
        $where = array(
            "_logic" => "OR",
            array(
                "WorkflowProcess.workflow_id" => $theWorkflows[$model],
                "WorkflowProcess.mainrow_id"  => $id,
            )
        );
//        echo $workflowModel->getLastSql();
//        print_r($theWorkflows);exit;
        if($relationModels) {
            $relationModels = explode(",", $relationModels);
            foreach($relationModels as $rm) {
                $tmpMap = array(
                    $relationMainrowField => $id,
                    "source_model" => $model
                );
                $tmpModel = D($rm);
                $relationItem = $tmpModel->where($tmpMap)->find();
                if($relationItem) {
                    array_push($where, array(
                        "WorkflowProcess.workflow_id" => $theWorkflows[$rm],
                        "WorkflowProcess.mainrow_id"  => $relationItem["id"],
                    ));
                }
            }
        }
        
//        print_r($where);exit;
        
        $processViewModel = D("WorkflowProcessView");
        
        $map = array(
            "_complex" => $where
        );
        return $processViewModel->where($map)->order("start_time ASC")->select();
//        echo $processViewModel->getLastSql();exit;
        
    }
    
    /**
     * 检查用户可执行权限
     * eg: g:1,2|d:3,4|u:5,6
     * g: group
     * d: department
     * u: user
     */
    private function checkExecutorPermission($rules) {
        $rules = explode("|", $rules);
        $user = $_SESSION["user"];
        foreach($rules as $item) {
            list($k, $rule) = explode(":", $item);
            $ids = explode(",", $rule);
            switch($k) {
                case "g":
                    foreach($ids as $g) {
                        if(in_array($g, $user["group_ids"])) {
                            return true;
                        }
                    }
                    break;
                case "d":
                    if(in_array($user["Department"]["id"], $ids)) {
                        return true;
                    }
                    break;
                case "u":
                    if(in_array($user["id"], $ids)) {
                        return true;
                    }
                    break;
            }
        }
        
        return false;
    }
    
    /**
     * 检查节点权限
     */
    private function checkCondition($mainrowId, $node) {
        $file = sprintf("@.Workflow.%s.%s", $this->currentWorkflow["workflow_file"], $node["execute_file"]);
        import("@.Workflow.WorkflowInterface");
        import("@.Workflow.WorkflowAbstract");
        $rs = import($file);
        $className = $this->currentWorkflow["workflow_file"].$node["execute_file"];
        if(!$className or !class_exists($className)) {
            return true;
        }
//        print_r($node);exit;
        $node["context"] = unserialize($node["context"]);
        $obj = new $className($mainrowId);
        $rs = $obj->checkPermission($node["condition"]);
        return $rs === false ? false : true;
//        exit;
    }
    
}

?>